﻿#region File and License Information
/*
<File>
	<Copyright>Copyright © 2007, Daniel Vaughan. All rights reserved.</Copyright>
	<License see="prj:///Documentation/License.txt"/>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2009-02-08 18:10:38Z</CreationDate>
	<LastSubmissionDate>$Date: $</LastSubmissionDate>
	<Version>$Revision: $</Version>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;

namespace DanielVaughan.ComponentModel
{
	/// <summary>
	/// This class provides an implementation of the <see cref="INotifyPropertyChanged"/>
	/// interface.
	/// </summary>
	public sealed class PropertyChangedNotifier : INotifyPropertyChanged
	{
		readonly object owner;

		/// <summary>
		/// Initializes a new instance of the <see cref="PropertyChangedNotifier"/> class.
		/// </summary>
		/// <param name="owner">The intended sender of the <code>PropertyChanged</code> event.</param>
		public PropertyChangedNotifier(object owner)
		{
			ArgumentValidator.AssertNotNull(owner, "owner");
			this.owner = owner;
		}

		#region event PropertyChanged

		event PropertyChangedEventHandler propertyChanged;

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged
		{
			add
			{
				propertyChanged += value;
			}
			remove
			{
				propertyChanged -= value;
			}
		}

		void OnPropertyChanged(PropertyChangedEventArgs e)
		{
			if (propertyChanged != null)
			{
				propertyChanged(owner, e);
			}
		}

		public void OnPropertyChanged(string propertyName)
		{
			ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
			OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
		}

		#endregion

		readonly Dictionary<string, string> expressions = new Dictionary<string, string>();
		readonly ReaderWriterLockSlim expressionsLock = new ReaderWriterLockSlim();

		/* Slow. Not recommended. */
		public void OnPropertyChanged<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			ArgumentValidator.AssertNotNull(expression, "expression");

			string name;
			expressionsLock.EnterUpgradeableReadLock();
			try
			{
				if (!expressions.TryGetValue(expression.ToString(), out name))
				{
					expressionsLock.EnterWriteLock();
					try
					{
						if (!expressions.TryGetValue(expression.ToString(), out name))
						{
							var memberInfo = GetMemberInfo(expression);
							if (memberInfo == null)
							{
								throw new InvalidOperationException("MemberInfo not found."); /* TODO: Make localizable resource. */
							}
							name = memberInfo.Name;
							expressions.Add(expression.ToString(), name);
						}
					}
					finally
					{
						expressionsLock.ExitWriteLock();
					}
				}
			}
			finally
			{
				expressionsLock.ExitUpgradeableReadLock();
			}
			OnPropertyChanged(name);
		}

		static MemberInfo GetMemberInfo<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			var member = expression.Body as MemberExpression;
			if (member != null)
			{
				return member.Member;
			}

			throw new ArgumentException("MemberExpression expected.", "expression"); /* TODO: Make localizable resource. */
		}
	}
}